/**
 * @author salim & sidyous
 */

import java.io.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SharedObject implements Serializable, SharedObject_itf {
	
	/*
	 * Attributes
	 */
	
	// Core
	// Step 3: the attribute obj is never serialized 
	public transient Object obj;
	public int id;
	private int lock;
	
	// Synchronization
	Lock psy;
	Lock chik;
	Condition crw;
	
	// Constant
	static final int NL = 0;
	static final int RLC = 1;
	static final int RLT = 2;
	static final int WLC = 3;
	static final int WLT = 4;
	static final int RLT_WLC = 5;

	// Serialization
	private static final long serialVersionUID = 1L;
	// Where am I?!
	private boolean isOnServer = true;
	/*
	 * Methods
	 */
	
	// Constructor
	public SharedObject(Object obj, int id)
	{
		super();

		if (Debug.is_on) System.out.println("SharedObject : SharedObject " + id);
		
		this.obj = obj;
		this.id = id;
		lock = NL;
		psy = new ReentrantLock();
		chik = new ReentrantLock();
		crw = psy.newCondition();
	}

	// invoked by the user program on the client node
	public void lock_read()
	{
		if (Debug.is_on) System.out.println("SharedObject : lock_read");
		
		chik.lock();
		psy.lock();
		
		if (Debug.is_on) System.out.println("SharedObject : lock_read : CRITICAL SECTION");
		if (Debug.is_on) System.out.println(lock);
		
		switch(lock)
		{
		case NL:
			obj = Client.lock_read(id);
			lock = RLT;
			break;
		case RLC:
			lock = RLT;
			break;
		case RLT:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.lock_read() : Doesn't handle imbricated locks");
			break;
		case WLC:
			lock = RLT_WLC;
			break;
		case WLT:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.lock_read() : Doesn't handle imbricated locks");
			break;
		case RLT_WLC:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.lock_read() : Doesn't handle imbricated locks");
			break;
		}
		
		psy.unlock();
		chik.unlock();
		
		if (Debug.is_on) System.out.println("SharedObject : lock_read : CRITICAL SECTION OUT");
	}

	// invoked by the user program on the client node
	public void lock_write()
	{
		if (Debug.is_on) System.out.println("SharedObject : lock_write");
		
		boolean b = false;
		Object tmp;
		
		while (!b)
		{
			chik.lock();
			psy.lock();
			if (Debug.is_on) System.out.println("SharedObject : lock_write : CRITICAL SECTION");
			if (Debug.is_on) System.out.println(lock);
			switch (lock)
			{
			case NL:
				tmp = Client.lock_write(id);
				if (tmp == null) break;
				obj = tmp;
				lock = WLT;
				b = true;
				break;
			case RLC:
				tmp = Client.lock_write(id);
				if (tmp == null) break;
				obj = tmp;
				lock = WLT;
				b = true;
				break;
			case RLT:
				if (Debug.is_on) System.out.println("ERROR : SharedObject.lock_write() : doesn't handle imbricated locks");
				b = true;
				break;
			case WLC:
				lock = WLT;
				b = true;
				break;
			case WLT:
				if (Debug.is_on) System.out.println("ERROR : SharedObject.lock_write() : doesn't handle imbricated locks");
				b = true;
				break;
			case RLT_WLC:
				if (Debug.is_on) System.out.println("ERROR : SharedObject.lock_write() : doesn't handle imbricated locks");
				b = true;
				break;
			}

			if (Debug.is_on) System.out.println("SharedObject : lock_write : B = " + b + " obj = " + obj);
			
			psy.unlock();
			chik.unlock();
			
			if (Debug.is_on) System.out.println("SharedObject : lock_write : CRITICAL SECTION OUT");
		}
	}

	// invoked by the user program on the client node
	public synchronized void unlock()
	{
		if (Debug.is_on) System.out.println("SharedObject : unlock");
		
		psy.lock();
		
		if (Debug.is_on) System.out.println("SharedObject : unlock : CRITICAL SECTION");
		if (Debug.is_on) System.out.println(lock);
		
		switch(lock)
		{
		case NL:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.unlock() : Lock not taken");
			break;
		case RLC:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.unlock() : Lock not taken");
			break;
		case RLT:
			lock = RLC;
			break;
		case WLC:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.unlock() : Lock not taken");
			break;
		case WLT:
			lock = WLC;
			break;
		case RLT_WLC:
			lock = WLC;
			break;
		}
		
		crw.signalAll();
		psy.unlock();
		
		if (Debug.is_on) System.out.println("SharedObject : unlock : CRITICAL SECTION OUT");
	}


	// callback invoked remotely by the server
	public  Object reduce_lock()
	{
		if (Debug.is_on) System.out.println("SharedObject : reduce_lock");

		chik.lock();
		psy.lock();

		if (Debug.is_on) System.out.println("SharedObject : reduce_lock : CRITICAL SECTION");
		if (Debug.is_on) System.out.println(lock);
		
		switch(lock)
		{
		case NL:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.reduce_lock() : Lock not taken");
			break;
		case RLC:
			lock = NL;
			break;
		case RLT:
			crw.awaitUninterruptibly();
			lock = NL;
			break;
		case WLC:
			lock = RLC;
			break;
		case WLT:
			crw.awaitUninterruptibly();
			lock = RLC;
			break;
		case RLT_WLC:
			crw.awaitUninterruptibly();
			lock = RLC;
			break;
		}
		
		psy.unlock();
		
		if (Debug.is_on) System.out.println("SharedObject : reduce_lock : CRITICAL SECTION OUT");
		
		chik.unlock();
		
		return obj;
	}

	// callback invoked remotely by the server
	public  void invalidate_reader()
	{
		if (Debug.is_on) System.out.println("SharedObject : invalidate_reader");
		
		chik.lock();
		psy.lock();
		
		if (Debug.is_on) System.out.println("SharedObject : invalidate_reader : CRITICAL SECTION");
		if (Debug.is_on) System.out.println(lock);
		
		switch(lock)
		{
		case NL:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.invalidate_reader() : No reading rights");
			break;
		case RLC:
			lock = NL;
			break;
		case RLT:
			crw.awaitUninterruptibly();
			lock = NL;
			break;
		case WLC:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.invalidate_reader() : need to call invalidate_writer instead");
			break;
		case WLT:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.invalidate_reader() : need to call invalidate_writer instead");
			break;
		case RLT_WLC:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.invalidate_reader() : need to call invalidate_writer instead");
			break;
		}
		
		psy.unlock();
		chik.unlock();
		
		if (Debug.is_on) System.out.println("SharedObject : invalidate_reader : CRITICAL SECTION OUT");
	}

	public  Object invalidate_writer()
	{
		if (Debug.is_on) System.out.println("SharedObject : invalidate_writer");
		
		chik.lock();
		psy.lock();
		
		if (Debug.is_on) System.out.println("SharedObject : invalidate_writer : CRITICAL SECTION");
		if (Debug.is_on) System.out.println(lock);
		
		switch(lock)
		{
		case NL:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.invalidate_writer() : No writing rights");
			break;
		case RLC:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.invalidate_writer() : No writing rights");
			break;
		case RLT:
			if (Debug.is_on) System.out.println("ERROR : SharedObject.invalidate_writer() : No writing rights");
			break;
		case WLC:
			lock = NL;
			break;
		case WLT:
			crw.awaitUninterruptibly();
			lock = NL;
			break;
		case RLT_WLC:
			if (Debug.is_on) System.out.println("SharedObject : invalidate_writer : WAIT");
			crw.awaitUninterruptibly();
			if (Debug.is_on) System.out.println("SharedObject : invalidate_writer : SIGNALed");
			lock = NL;
			break;
		}
		
		psy.unlock();
		chik.unlock();
		
		if (Debug.is_on) System.out.println("SharedObject : invalidate_writer : CRITICAL SECTION OUT");
		
		return obj;
	}
	
	/**
	 * Rewriting of the method readResolve
	 * return Object;
	 */
	public Object readResolve() throws ObjectStreamException {
		
		if(this.isOnServer) {
			System.out.println("Deserializing SharedObject on Server");
		}else {
			System.out.println("Deserializing SharedObject on Client");
		}
		
		/*
		 * We need to know whether the sharedObject is deserialized on the
		 * server or the client side.
		 */
		
		if (this.isOnServer) {
			// The shareObject is deserialized on the server
			// Nothing special to do. We just change the attribute isOnServer
			// because the shared object will next be deserialized where a client
			// will look it up.
			this.isOnServer = false;
		}else {
			// The shared Object is deserialized on a client.
			// We first change the attribute isOnServer for the opposite reasons
			this.isOnServer = true;
			if (Client.containsKey(id)) {
				
				System.out.println("le SharedObject est déja chez client");
				
				return Client.getObject(id);
			}else {
				
				System.out.println("le SharedObject n'est pas chez le client, on le récupère du serveur et on le met NL");
				
				// NL is important. This forces client to get the object from the server
				// when an application is locks the object.
				this.lock = NL;
				if(this.lock == NL ){
					System.out.println(" le sharedObject est mis nolock");
				}
				// put the sharedObject in the the sharedObjectMap of Client
				Client.put(id, this);
			}
		}
		// We return the right sharedObject;
		return this;
	}
}
